package fhffm.DagmAI.model;

import java.util.BitSet;
import java.util.LinkedHashMap;
import java.util.Set;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import fhffm.DagmAI.exceptions.LectureNotFoundException;
import fhffm.DagmAI.exceptions.ProfNotFoundException;

public class IndividualUtil {

	private BiMap<BitSet, Professor> profMap;
	private BiMap<BitSet, Lecture> lectureMap;

	private int lectureGenoLength;
	private int profGenoLength;
	private int totalFieldLength;

	public IndividualUtil(Set<Professor> profs, Set<Lecture> lectures) {
		super();

		profGenoLength = getBitnumberForEncoding(profs.size());
		lectureGenoLength = getBitnumberForEncoding(lectures.size());
		totalFieldLength = profGenoLength + lectureGenoLength;

		setProfMap(getBitRepresentation(profs));
		setLectureMap(getBitRepresentation(lectures));
	}

	public BitSet encode(LinkedHashMap<Lecture, Professor> toEncode) {
		BitSet genoType = new BitSet(totalFieldLength * toEncode.size());

		int copyIndex = 0;

		for (Lecture bs : toEncode.keySet()) {
			if (getLectureMap().containsValue(bs)) {

				BitSet lecture = getLectureMap().inverse().get(bs);
				BitSet prof = getProfMap().inverse().get(toEncode.get(bs));

				for (int i = copyIndex; i < copyIndex + lectureGenoLength; i++)
					genoType.set(i, lecture.get(i));

				copyIndex += lectureGenoLength;

				for (int i = copyIndex; i < copyIndex + profGenoLength; i++)
					genoType.set(i, prof.get(i));

				copyIndex += profGenoLength;
			}
		}

		return genoType;
	}

	public LinkedHashMap<Lecture, Professor> decode(BitSet genoType)
			throws LectureNotFoundException, ProfNotFoundException {
		LinkedHashMap<Lecture, Professor> phenoType = new LinkedHashMap<Lecture, Professor>();

		for (int i = 0; i < genoType.length(); i++) {
			BitSet lectureKey = genoType.get(i, i + lectureGenoLength);
			i += lectureGenoLength;

			BitSet profKey = genoType.get(i, i + profGenoLength);
			i += profGenoLength;

			Lecture lecture = getLectureMap().get(lectureKey);
			Professor prof = getProfMap().get(profKey);

			if (lecture == null) {
				throw new LectureNotFoundException();
			} else if (prof == null) {
				throw new ProfNotFoundException();
			} else {
				phenoType.put(lecture, prof);
			}
		}
		return phenoType;
	}

	public static int getBitnumberForEncoding(int numberOfObjectsToEncode) {
		int size = numberOfObjectsToEncode;
		if (size == 0)
			return 0;

		int numBits = 0;
		while (true) {
			size = size - ((int) Math.pow(2, numBits));
			numBits++;
			if (size <= 0) {
				break;
			}
		}
		return numBits;
	}

	public static <E> BiMap<BitSet, E> getBitRepresentation(Set<E> phenos) {
		BiMap<BitSet, E> mapping = HashBiMap.create();
		byte counter = 0;
		for (E pheno : phenos) {
			mapping.put(BitSet.valueOf(new byte[] { counter }), pheno);
			counter++;
		}
		return mapping;
	}

	public BiMap<BitSet, Professor> getProfMap() {
		return profMap;
	}

	public void setProfMap(BiMap<BitSet, Professor> profMap) {
		this.profMap = profMap;
	}

	public BiMap<BitSet, Lecture> getLectureMap() {
		return lectureMap;
	}

	public void setLectureMap(BiMap<BitSet, Lecture> lectureMap) {
		this.lectureMap = lectureMap;
	}
}
